using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Threading;

namespace Ioc.Kernel.LifeStyles
{
    public class ThreadLifeStyleStrategy : DefaultLifeStyleStrategy
    {
        private readonly object workerLocker;
        private int workerCount;
        private readonly IDictionary<Thread, object> threadInstances;
        private readonly Timer GarbageCollectorTimer;
        private readonly int estimatedComponentLifetime;

        #region Component garbage collector

        /// <summary>
        /// Initializes a new instance of the <see cref="ThreadLifeStyleStrategy"/> class.
        /// </summary>
        //public ThreadLifeStyleStrategy() : this(120000){ 
        public ThreadLifeStyleStrategy() : this(1200){ 
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ThreadLifeStyleStrategy"/> class.
        /// </summary>
        /// <param name="estimatedComponentLifetime">The estimate component lifetime.</param>
        public ThreadLifeStyleStrategy(int estimatedComponentLifetime)
        {
            workerLocker = new object();
            threadInstances = new Dictionary<Thread, object>();
            this.estimatedComponentLifetime = estimatedComponentLifetime;
            GarbageCollectorTimer = new Timer(ComponentGarbageCollector);
            GarbageCollectorTimer.Change(0, estimatedComponentLifetime); 
        }

        /// <summary>
        /// Deletes the dead thread's entries
        /// </summary>
        /// <param name="state">The state.</param>
        private void ComponentGarbageCollector(object state)
        {
            var entriesToDelete = new List<Thread>();
            lock (workerLocker)
            {
                foreach (var value in threadInstances.Keys)
                {
                    if (!value.IsAlive)
                        entriesToDelete.Add(value);
                }
                
                foreach (var thread in entriesToDelete)
                {
                    threadInstances.Remove(thread);
                    --workerCount;
                    Monitor.Pulse(workerLocker);
                }
            }
        }

        #endregion
        
        /// <summary>
        /// Musts the instanciate.
        /// </summary>
        /// <returns></returns>
        public override bool MustInstanciate()
        {
            lock (workerLocker)
            {
                return !threadInstances.ContainsKey(Thread.CurrentThread);
            }
        }

        /// <summary>
        /// Gets the expression.
        /// </summary>
        /// <param name="className"></param>
        /// <returns></returns>
        public override Expression GetExpression(Type className)
        {
            var currentThread = Thread.CurrentThread;

            lock (workerLocker)
            {
                if (!threadInstances.ContainsKey(currentThread)) 
                    return null;

                return Expression.Constant(threadInstances[currentThread], className);
            }
        }

        /// <summary>
        /// Sets the expression.
        /// </summary>
        /// <param name="expression">The expression.</param>
        public override void SetExpression(Expression expression)
        {
            var currentThread = Thread.CurrentThread;
            lock (workerLocker)
            {
                if (!threadInstances.ContainsKey(currentThread))
                {
                    var func = Expression.Lambda<Func<object>>(expression).Compile();
                    threadInstances.Add(currentThread, func());
                    ++workerCount;
                    Monitor.Pulse(workerLocker);
                }
            }
        }

        /// <summary>
        /// Releases unmanaged and - optionally - managed resources
        /// </summary>
        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
        public override void Dispose(bool disposing)
        {
            if (!disposing) return;

            lock (workerLocker)
            {
                while (workerCount > 0)
                    Monitor.Wait(workerLocker, estimatedComponentLifetime);
            }
            threadInstances.Clear();
        }
    }
}